package pa2;

import java.rmi.RemoteException;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

import pa2.system.PeerImpl;
import pa2.util.Callback;
import pa2.util.Event;
import pa2.util.ExcelWrite;
import pa2.util.CompInteger;
import pa2.util.LogCallback;
import pa2.util.PeerNameEvent;



public class Main {
	public static Map<Long, Integer> delayTimes = Collections.synchronizedMap(
			new HashMap<Long,Integer>());
	public static Map<String, Integer> peerCounts = Collections.synchronizedMap(
			new HashMap<String,Integer>());
	public static volatile long maxDelay = 0;
	private static CustomSystemManager systemManager;
	/**
	 * @param args
	 */
	public static void main(String[] args) {		
		try {
			systemManager = new CustomSystemManager();
			systemManager.run();

			new Thread(new PeerImpl("A", 1)).run();
			new Thread(new PeerImpl("B", 1)).run();
			new Thread(new PeerImpl("C", 1)).run();
			new Thread(new PeerImpl("D", 1)).run();

			//open a new excel file where the results will be stored
			ExcelWrite ew = new ExcelWrite();
			ew.setInputFile("files/results1.xls");

			Thread.sleep(5000);
			
			// TEST 1: Lookup Latency
			{
				//execute test 1 1000 times
				maxDelay = 0;
				delayTimes.clear();
				for(int i = 0; i < 1000; i++){
					testLookupLatencyOnEmptyDHT(systemManager);
				}
				Thread.sleep(5000);
				//write the results of test 1 into the excel file
				int row = 0;
				for(long i = 0; i < maxDelay; i++){
					if(delayTimes.get(i) != null){
						//System.out.println(i + ": " + delayTimes.get(i));
						ew.addNumber(0, row, (long) i);
						ew.addNumber(1, row, (long) delayTimes.get(i));
						row++;
					}					
				}
				System.out.println("test 1 completed");
			}
			
//			Thread.sleep(5000);

			//TEST 2: Insert Latency
			{
				//execute test 2 1000 times
				maxDelay = 0;
				delayTimes.clear();
				for(int i = 0; i < 1000; i++){
					testInsertionLatency(systemManager);
				}
				Thread.sleep(5000);
				//write the results of test 2 into the excel file
				int row = 0;
				for(long i = 0; i < maxDelay; i++){
					if(delayTimes.get(i) != null){
						//System.out.println(i + ": " + delayTimes.get(i));
						ew.addNumber(3, row, (long) i);
						ew.addNumber(4, row, (long) delayTimes.get(i));
						row++;
					}					
				}
				System.out.println("test 2 completed");
			}
			
//			Thread.sleep(5000);

			//TEST 3: Storage Distribution
			{
				//execute test 3 
				peerCounts.clear();
				//at first, insert 1000 random values
				for(int i = 0; i < 1000; i++){
					Random r = new Random();
					int key = r.nextInt(CustomSystemManager.RANGE);
					systemManager.put(new CompInteger(key), "value" + i, new LogCallback<CompInteger,String>(){
							private String lastPeerName = "";
							
							@Override
							public void call(CompInteger key, String returnValue)
									throws RemoteException {
//								System.out.println(lastPeerName);
		                        if(peerCounts.get(lastPeerName) == null){
	                                peerCounts.put(lastPeerName, 1);
		                        } else {
		                        	peerCounts.put(lastPeerName, peerCounts.get(lastPeerName) + 1);
		                        }
							}
							
							@Override
							public void logEvent(Event event)
									throws RemoteException {
								if (event instanceof PeerNameEvent) {
									lastPeerName = ((PeerNameEvent) event).getPeerName();
								}
							}} );
				}
				Thread.sleep(5000);
				System.out.println("Stored Keys on node A: " + peerCounts.get("A"));
				System.out.println("Stored Keys on node B: " + peerCounts.get("B"));
				System.out.println("Stored Keys on node C: " + peerCounts.get("C"));
				System.out.println("Stored Keys on node D: " + peerCounts.get("D"));
				System.out.println("test 3 completed");
			}
			
//			Thread.sleep(5000);
			
			//TEST 4: Message Overhead
			{
				//execute test 4
				peerCounts.clear();
				for(int i = 0; i < 30000; i++){
					Random r = new Random();
					int key = r.nextInt(CustomSystemManager.RANGE);
					CompInteger comp = new CompInteger(key);
					systemManager.get(comp,new LogCallback<CompInteger,String>(){
						private String lastPeerName = "";
						
						@Override
						public void call(CompInteger key, String returnValue)
								throws RemoteException {
						}

						@Override
						public void logEvent(Event event)
								throws RemoteException {
							String peerName = "";
							if (event instanceof PeerNameEvent) {
								peerName = ((PeerNameEvent) event).getPeerName();
							}
							//only log messages between different nodes
							if(!lastPeerName.equals(peerName)){
		                        if(peerCounts.get(peerName) == null){
	                                peerCounts.put(peerName, 1);
		                        } else {
		                        	peerCounts.put(peerName, peerCounts.get(peerName) + 1);
		                        }
							}
							lastPeerName = peerName;
						}} );
				}
				Thread.sleep(5000);
				System.out.println("Received Messages on node A: " + peerCounts.get("A"));
				System.out.println("Received Messages on node B: " + peerCounts.get("B"));
				System.out.println("Received Messages on node C: " + peerCounts.get("C"));
				System.out.println("Received Messages on node D: " + peerCounts.get("D"));
				System.out.println("test 4 completed");
			}
			
			ew.save();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Test 1: Lookup latency for a random key in an empty data structure
	 * @param systemManager
	 * @throws RemoteException
	 */
	private static void testLookupLatencyOnEmptyDHT(CustomSystemManager systemManager) throws RemoteException{
		Random random = new Random();
		int key = random.nextInt(CustomSystemManager.RANGE);
		//System.out.println("Test 1: Looking for key " + key + " in empty DHT:");
		CompInteger comp = new CompInteger(key);
		systemManager.get(comp, new Callback<CompInteger,String>(){
            long start = System.currentTimeMillis();

			@Override
			public void call(CompInteger key, String returnValue)
					throws RemoteException {
                long lookupLatency = System.currentTimeMillis() - start;
                if(lookupLatency > Main.maxDelay)
                        Main.maxDelay = lookupLatency;
                if(Main.delayTimes.get(lookupLatency) != null)
                        Main.delayTimes.put(lookupLatency, Main.delayTimes.get(lookupLatency) + 1);
                else Main.delayTimes.put(lookupLatency, 1);  
			}});
	}

	/**
	 * Test 2: Insert Latency for a random key in an empty data structure
	 * @param systemManager
	 * @throws RemoteException
	 */
	private static void testInsertionLatency(CustomSystemManager systemManager) throws RemoteException{
		Random random = new Random();
		int key = random.nextInt(CustomSystemManager.RANGE);
		CompInteger comp = new CompInteger(key);
		systemManager.put(comp, "test", new Callback<CompInteger,String>(){
            long start = System.currentTimeMillis();
            
			@Override
			public void call(CompInteger key, String returnValue)
					throws RemoteException {
                long insertionLatency = System.currentTimeMillis() - start;
                if(insertionLatency > Main.maxDelay)
                        Main.maxDelay = insertionLatency;
                if(Main.delayTimes.get(insertionLatency) != null)
                        Main.delayTimes.put(insertionLatency, Main.delayTimes.get(insertionLatency) + 1);
                else Main.delayTimes.put(insertionLatency, 1);
                
                //remove the value to clear the data structure again
                Main.systemManager.remove(key, new Callback<CompInteger,String>(){
                	
					@Override
					public void call(CompInteger key, String returnValue)
							throws RemoteException {
					}});
                //System.out.println("Insertion latency: " + insertionLatency);   
			}});
	}
}
